"""
@Time ： 2025/8/26 14:45
@Auth ： zh
@File ：test2.py
"""

# 功能：获取项目所有主材的合价总和
# 实现逻辑：通过调用三个API接口，分步获取数据，最终计算符合条件的合价总和

# 导入所需库
import requests  # 用于发送HTTP请求
import json      # 用于处理JSON数据
from collections import defaultdict  # 用于记录(billId, code)的匹配次数，方便嵌套字典计数

# 定义三个API的URL地址
URL1 = "https://test.icii.agtsci.cn/api/cis/quota/subBill/library/listSubBill"  # 获取子清单列表
URL2 = "https://test.icii.agtsci.cn/api/cis/quota/subBill/library/findBySubBillId"  # 根据子清单ID获取详情
URL3 = "https://test.icii.agtsci.cn/api/cis/quota/collect/bill/billDetail"  # 获取清单详情（包含合价信息）



# 请求头配置，根据实际接口要求可能需要调整
headers = {
    "Content-Type": "application/json",  # 声明请求体为JSON格式
    # 认证信息，实际使用中需替换为有效的token
    "Authorization": "bearer 231fb43c-9f1d-43de-89e0-3f35a45d269f"
}


def get_api_data(url, params=None):
    """
    发送POST请求并返回JSON数据

    参数:
        url (str): 请求的API地址
        params (dict, optional): 请求参数（JSON格式）。默认为None

    返回:
        dict: 接口返回的JSON数据（解析后的字典），若请求失败或解析错误则返回None
    """
    try:
        # 发送POST请求，携带 headers 和 JSON 参数
        response = requests.post(url, headers=headers, json=params)
        response.raise_for_status()  # 若响应状态码不是200，抛出HTTPError异常
        return response.json()  # 解析JSON响应并返回
    except requests.exceptions.RequestException as e:
        print(f"请求错误: {e}")  # 捕获请求相关异常（如网络错误、超时等）
        return None
    except json.JSONDecodeError:
        print("无法解析JSON响应")  # 捕获JSON解析失败异常
        return None


def extract_first_total_cost(node, target_code):
    """
    递归提取第一个匹配目标code的节点的totalCost值，找到后立即返回

    参数:
        node (dict): 要遍历的节点（字典类型，可能包含children子节点）
        target_code (str): 要匹配的code值

    返回:
        float: 匹配节点的totalCost值（转换为浮点数），若未找到或转换失败则返回None
    """
    # 检查当前节点是否匹配目标code
    if node.get("code") == target_code:
        # 尝试提取totalCost并转换为浮点数
        try:
            return float(node.get("totalCost", 0))
        except ValueError:
            print(f"无法转换totalCost为数字: {node.get('totalCost')}")
            return None

    # 递归遍历当前节点的children（若存在且为列表）
    children = node.get("children")
    if children and isinstance(children, list):
        for child in children:
            result = extract_first_total_cost(child, target_code)
            if result is not None:  # 找到第一个匹配项后立即返回，不再继续查找
                return result
    return None  # 未找到匹配项


def main():
    """主函数：协调各步骤执行，实现数据获取、处理和结果计算"""
    # 基础请求参数，pageNumber会在分页处理时动态调整
    base_params = {
        "name": "",
        "billName": "",
        "billDesc": "",
        "saveParam": "0",
        "orderFields": [],
        "type": "1",
        "paramJsonStr": [
            "佛山监狱学习及训练用房21321"
        ],
        "quotaIds": [
            "7368114622919999488"
        ],
        "pageParamJson": {
            "paramJsonStr": [
                "佛山监狱学习及训练用房21321"
            ],
            "quotaIds": [
                "7368114622919999488"
            ],
            "saveParam": 0
        },
        "fileKindList": [],
        "regionIdList": [],
        "projectCategoryIds": [],
        "startTime": "",
        "endTime": "",
        "pageSize": 100  # 每页固定返回100条数据
    }

    # 存储提取到的所有有效totalCost值
    total_cost_list = []
    # 记录每个billId的处理次数（用于定位children列表中的索引）
    bill_id_counter = {}
    # 新增：记录每个(billId, code)组合的匹配次数（嵌套字典：{bill_id: {code: 已匹配次数}}）
    bill_code_counter = defaultdict(lambda: defaultdict(int))
    page_number = 1  # 起始页码，从1开始
    total_cycles = 0  # 总数据量（需要处理的总条数）
    processed_count = 0  # 已处理的数据条数

    # 先获取总数据量，用于判断分页终止条件
    print("获取总数据量...")
    first_page_params = base_params.copy()  # 复制基础参数
    first_page_params["pageNumber"] = 1     # 设置为第1页
    response_first = get_api_data(URL1, first_page_params)  # 发送请求

    # 检查请求结果是否有效
    if not response_first or response_first.get("code") != 200:
        print("获取总数据量失败")
        return  # 失败则退出程序

    # 提取总数据量
    total_cycles = int(response_first.get("data", {}).get("total", 0))
    print(f"共需处理 {total_cycles} 条数据")

    # 分页处理所有数据（直到处理完所有数据）
    while processed_count < total_cycles:
        print(f"\n===== 处理第 {page_number} 页 =====")
        # 设置当前页的请求参数
        current_params = base_params.copy()
        current_params["pageNumber"] = page_number

        # 1. 发送第一个请求（获取当前页的子清单列表）
        print("发送第一个请求...")
        response1 = get_api_data(URL1, current_params)

        # 检查请求结果是否有效
        if not response1 or response1.get("code") != 200:
            print("第一个请求失败或返回错误，跳过当前页")
            page_number += 1  # 继续处理下一页
            continue

        # 提取当前页的数据列表
        data1 = response1.get("data", {})
        list_items = data1.get("list", [])
        current_page_size = len(list_items)  # 当前页的实际数据条数

        # 若当前页无数据，结束处理
        if current_page_size == 0:
            print("当前页无数据，结束处理")
            break

        # 2. 循环处理当前页的每条数据
        for item in list_items:
            processed_count += 1  # 已处理数量+1
            print(f"\n处理第 {processed_count}/{total_cycles} 条数据")

            # 提取当前条目的billId和id（用于后续请求）
            bill_id = item.get("billId")
            item_id = item.get("id")

            # 若缺少必要参数，跳过当前条目
            if not bill_id or not item_id:
                print("缺少billId或id，跳过此条数据")
                continue

            print(f"提取到 billId: {bill_id}, id: {item_id}")

            # 计算当前billId的处理次数（从1开始计数）
            current_count = bill_id_counter.get(bill_id, 0) + 1
            bill_id_counter[bill_id] = current_count  # 更新计数器
            print(f"当前是billId {bill_id} 的第 {current_count} 次处理")

            # 3. 发送第二个请求（根据id获取目标code）
            print("发送第二个请求...")
            params2 = {"id": item_id}  # 请求参数为当前条目的id
            response2 = get_api_data(URL2, params2)

            # 检查请求结果是否有效
            if not response2 or response2.get("code") != 200:
                print("第二个请求失败或返回错误，跳过此条数据")
                continue

            # 提取目标code（用于后续匹配合价）
            data2 = response2.get("data", {})
            target_code = data2.get("code")

            # 若未获取到目标code，跳过当前条目
            if not target_code:
                print("未找到目标code，跳过此条数据")
                continue

            print(f"提取到目标code: {target_code}")

            # 新增：获取当前(billId, target_code)组合的已匹配次数
            current_code_count = bill_code_counter[bill_id][target_code]
            print(f"当前是billId {bill_id} 下 code {target_code} 的第 {current_code_count + 1} 次匹配")

            # 4. 发送第三个请求（根据billId获取合价详情）
            print("发送第三个请求...")
            params3 = {"billId": bill_id}  # 请求参数为当前条目的billId
            response3 = get_api_data(URL3, params3)

            # 检查请求结果是否有效
            if not response3 or response3.get("code") != 200:
                print("第三个请求失败或返回错误，跳过此条数据")
                continue

            # 提取合价信息列表
            data3 = response3.get("data", {})
            assemble_price_infos = data3.get("assemblePriceInfos", [])

            # 5. 处理assemblePriceInfos数据，提取目标totalCost（分步骤优化逻辑）
            current_total_cost = None  # 用于存储当前提取到的合价
            if assemble_price_infos:  # 确保合价信息列表不为空
                first_item = assemble_price_infos[0]  # 取列表中的第一个元素
                children_list = first_item.get("children", [])  # 获取第一层children列表
                print(f"第一层children总数: {len(children_list)}，当前需匹配第 {current_count} 个")

                # 步骤1：在首层children中收集所有匹配target_code的节点，按顺序选择对应次数的节点
                first_level_matches = [child for child in children_list if child.get("code") == target_code]
                print(f"首层匹配code {target_code} 的节点数量: {len(first_level_matches)}")

                # 若首层匹配节点数量足够当前所需次数，则直接提取
                if len(first_level_matches) > current_code_count:
                    # 选择第current_code_count个匹配节点（索引从0开始）
                    target_child = first_level_matches[current_code_count]
                    try:
                        current_total_cost = float(target_child.get("totalCost", 0))
                        print(f"在首层匹配节点中找到第 {current_code_count + 1} 个，提取totalCost: {current_total_cost}")
                        total_cost_list.append(current_total_cost)  # 加入结果列表
                        bill_code_counter[bill_id][target_code] += 1  # 更新匹配次数计数器
                    except ValueError:
                        print(f"首层第 {current_code_count + 1} 个匹配节点的totalCost不是有效数字: {target_child.get('totalCost')}")

                # 步骤2：若首层匹配节点不足，根据当前处理次数定位到对应的第一层children，再检查其第二层children
                if current_total_cost is None:
                    # 检查第一层children数量是否足够当前所需次数
                    if len(children_list) >= current_count:
                        # 获取当前次数对应的第一层children元素（索引从0开始，故-1）
                        target_first_child = children_list[current_count - 1]
                        print(f"根据处理次数定位到第一层children第 {current_count} 个元素（code: {target_first_child.get('code')}），开始检查其第二层children")

                        # 检查该元素的第二层children
                        second_children = target_first_child.get("children", [])
                        for second_child in second_children:
                            if second_child.get("code") == target_code:
                                try:
                                    current_total_cost = float(second_child.get("totalCost", 0))
                                    print(f"在对应第一层元素的第二层children中找到匹配code的节点，提取totalCost: {current_total_cost}")
                                    total_cost_list.append(current_total_cost)  # 加入结果列表
                                    bill_code_counter[bill_id][target_code] += 1  # 更新匹配次数计数器
                                    break  # 找到后退出第二层循环
                                except ValueError:
                                    print(f"第二层匹配节点的totalCost不是有效数字: {second_child.get('totalCost')}")
                                    break
                    else:
                        print(f"第一层children数量不足（共{len(children_list)}个），无法匹配第{current_count}个元素")

                # 步骤3：若前两步无匹配，执行原过滤纯数字code+按次数匹配逻辑
                if current_total_cost is None:
                    print(f"前两层均无匹配，过滤纯数字code后按处理次数查找第 {current_count} 个元素")
                    # 过滤首层children中code为纯数字的元素（排除纯数字code的节点）
                    filtered_children = []
                    for child in children_list:
                        child_code = child.get("code", "")
                        if not child_code.isdigit():  # 非纯数字code才保留
                            filtered_children.append(child)
                    print(f"过滤后children列表长度：{len(filtered_children)}（原长度：{len(children_list)}）")

                    # 若过滤后的列表长度足够当前所需次数
                    if len(filtered_children) >= current_count:
                        target_child = filtered_children[current_count - 1]  # 获取对应元素
                        # 递归查找该元素及其子节点中第一个匹配code的totalCost
                        current_total_cost = extract_first_total_cost(target_child, target_code)
                        if current_total_cost is not None:
                            total_cost_list.append(current_total_cost)  # 加入结果列表
                            bill_code_counter[bill_id][target_code] += 1  # 更新匹配次数计数器
                            print(f"通过递归找到totalCost: {current_total_cost}")
                        else:
                            print("递归未找到有效totalCost")
                    else:
                        print(f"过滤后children列表长度不足，无法获取第 {current_count} 个元素（当前长度：{len(filtered_children)}）")
            else:
                print("assemblePriceInfos列表为空，无法获取children")

        # 准备处理下一页
        page_number += 1

    # 6. 输出最终结果
    print("\n" + "=" * 50)
    print(f"处理完成，共提取 {len(total_cost_list)} 个有效totalCost值")
    print(f"totalCost列表: {total_cost_list}")
    # 计算并输出总和（避免空列表sum报错）
    print(f"totalCost总和: {sum(total_cost_list) if total_cost_list else 0}")


# 程序入口：当脚本直接运行时执行main函数
if __name__ == "__main__":
    main()